﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.ServiceProcess;
using System.Text;
using System.Threading;

namespace GPSAndroidProtocolListenerWindowsService
{
    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        public const int BufferSize = 512;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
    }      

    partial class GPSAndroidProtocolListenerWindowsService : ServiceBase
    {                  
        public const string WindowsServiceModel = "Android";
        public const int BufferSize = 512;
        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);
        Thread TCPListenerThread = new Thread(StartListening);
        public static bool IsThreadAlive = true;

        // Enum Field
        enum MessageField
        {
            ProtocolPrefix = 0,
            Imei = 1,
            Datetime = 2,            
            Longitude = 3,
            Latitude = 4,
            Heading = 5,
            Speed = 6,
            Signal = 7
        }

        public GPSAndroidProtocolListenerWindowsService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("--- GPS " + WindowsServiceModel + " Port Listener Starting V1 ---", WindowsServiceModel);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("IP: " + System.Net.Dns.GetHostName(), WindowsServiceModel);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Port: " + Properties.Settings.Default.Port.ToString(), WindowsServiceModel);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Max Connections: " + Properties.Settings.Default.MaxConnections.ToString(), WindowsServiceModel);

                IsThreadAlive = true;
                TCPListenerThread.Start();
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error..... Message:" + ex.Message + " StackTrace:" + ex.StackTrace, WindowsServiceModel);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("--- GPS " + WindowsServiceModel + " Port Listener Is DOWN", WindowsServiceModel);
            }
        }

        protected override void OnStop()
        {
            IsThreadAlive = false;
            GPSTrackingDBCustom.Log.WindowsServiceLog.Warning("--- GPS " + WindowsServiceModel + " Port Listener Is DOWN ---", WindowsServiceModel);
        }

        public static void StartListening()
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.Port);

            // Create a TCP/IP socket for listner.
            Socket listenerSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the endpoint and wait for listen for incoming connections.
            try
            {
                listenerSock.Bind(ep);
                listenerSock.Listen(Properties.Settings.Default.MaxConnections);                

                while (IsThreadAlive)
                {
                    try
                    {
                        // Set the event to nonsignaled state.
                        allDone.Reset();                        

                        // Start an asynchronous socket to listen for connections.
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Waiting for a connection ...", WindowsServiceModel);
                        listenerSock.BeginAccept(new AsyncCallback(AcceptCallback), listenerSock);

                        // Wait until a connection is made before continuing.
                        allDone.WaitOne();
                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - StartListening Waiting for a connection ... Message:" + ex.Message, WindowsServiceModel);
                        listenerSock.Shutdown(SocketShutdown.Both);
                        listenerSock.Close();
                    }

                }

            }
            catch (Exception ex)
            {
                IsThreadAlive = false;
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error..... Message:" + ex.Message, WindowsServiceModel);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error(WindowsServiceModel + " Port Listener is DOWN", WindowsServiceModel);
            }
        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                // Signal the main thread to continue.
                allDone.Set();

                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;
                Socket handler = listener.EndAccept(ar);                

                // Create the state object.
                StateObject MessageDevice = new StateObject();
                MessageDevice.workSocket = handler;
                handler.BeginReceive(MessageDevice.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), MessageDevice);
               
                
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - AcceptCallback Message:" + ex.Message, WindowsServiceModel);
            }
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject MessageDevice = (StateObject)ar.AsyncState;
            Socket handler = MessageDevice.workSocket;

            // Read data from the client socket.
            int bytesRead = handler.EndReceive(ar);

            try
            {

                //No Message 
                if (bytesRead <= 0)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... gps device. No Message", WindowsServiceModel);
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();

                    return;
                }

                //Process Message Android
                var New_MessageDevice = MessageDevice.buffer.TakeWhile((v, index) => MessageDevice.buffer.Skip(index).Any(w => w != 0x00)).ToArray();
                string Message = System.Text.UTF8Encoding.UTF8.GetString(New_MessageDevice);

                //Message Sample: imei,datetime,longitude,latitude,heading,speed
                //                123456789012345|01/01/2014 23:59:00|-84,123456789|10,123456789|180|000|G

                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Device: " + Message, WindowsServiceModel);
                string[] MessageArray = Message.Split(new char[] { '|' });

                if (MessageArray.Length != 8)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... gps device. Length wrong!", WindowsServiceModel);
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();

                    return;
                }

                string Imei = MessageArray[(int)MessageField.Imei];
                string Datetime = MessageArray[(int)MessageField.Datetime];
                string Latitude = MessageArray[(int)MessageField.Latitude];
                string Longitude = MessageArray[(int)MessageField.Longitude];
                string Heading = MessageArray[(int)MessageField.Heading];
                string Speed = MessageArray[(int)MessageField.Speed];
                string Signal = MessageArray[(int)MessageField.Signal]; //H - High, L - Low
               
                //Insert to Database
                GPSTrackingDBV2.Entities.TrackData TrackDataEntity = new GPSTrackingDBV2.Entities.TrackData();
                try
                {
                   
                    GPSTrackingDBV2.Entities.TrackDataLastLocation TrackDataLastLocationEntity = new GPSTrackingDBV2.Entities.TrackDataLastLocation();

                    DateTime DateFix = DateTime.ParseExact(Datetime, "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.CurrentCulture);

                    TrackDataLastLocationEntity.GpsImei = TrackDataEntity.GpsImei = Imei;
                    TrackDataLastLocationEntity.DateTime = TrackDataEntity.DateTime = DateFix;

                    double LatitudeFix = Convert.ToDouble(Latitude,  new System.Globalization.CultureInfo("en-US"));
                    double LongitudeFix = Convert.ToDouble(Longitude, new System.Globalization.CultureInfo("en-US"));
                    if (Properties.Settings.Default.AutoFixPosition)
                    {
                        LatitudeFix = LatitudeFix - Properties.Settings.Default.FixLatitude;
                        LongitudeFix = LongitudeFix - Properties.Settings.Default.FixLongitude;
                    }

                    TrackDataLastLocationEntity.Latitude = TrackDataEntity.Latitude = LatitudeFix;
                    TrackDataLastLocationEntity.Longitude = TrackDataEntity.Longitude = LongitudeFix;
                    TrackDataLastLocationEntity.Heading = TrackDataEntity.Heading= Convert.ToDouble(Heading, new System.Globalization.CultureInfo("en-US"));
                    TrackDataLastLocationEntity.Speed = TrackDataEntity.Speed = Convert.ToDouble(Speed, new System.Globalization.CultureInfo("en-US")) * 1.852;


                    try
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Inserting track message to database ...", WindowsServiceModel);
                        GPSTrackingDBV2.Data.DataRepository.TrackDataProvider.Insert(TrackDataEntity);
                    }
                    catch (Exception ex)
                    {
                        if (!ex.Message.Contains("Violation of PRIMARY KEY constraint"))
                        {
                            GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Inserting track message:" + ex.Message, WindowsServiceModel);
                        }

                    }

                    //Update last location  
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Updating last track message location to database ...", WindowsServiceModel);
                    try
                    {
                        //GPSTrackingDBV2Custom.Data.DataRepository.TrackDataLastLocationProvider.UpdateOrInsert(TrackDataLastLocationEntity);

                        // Disconnect Hand Held                        
                        Send(handler, Encoding.ASCII.GetBytes("OK"));

                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Updating last track message:" + ex.Message, WindowsServiceModel);
                        // Disconnect 
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();
                    }


                }
                catch (Exception ex)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Coordenates convert:" + ex.Message, WindowsServiceModel);
                    // Disconnect 
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();

                }               
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Message invalid:" + ex.Message, WindowsServiceModel);
                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
        }

        private static void Send(Socket handler, byte[] MessageSend)
        {
            try
            {
                // Begin sending the data to the remote device.
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Sending OK", WindowsServiceModel);
                handler.BeginSend(MessageSend, 0, MessageSend.Length, 0, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Send Message:" + ex.Message, WindowsServiceModel);
                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
        }

        private static void SendCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            Socket handler = (Socket)ar.AsyncState;
            try
            {
                // Complete sending the data to the remote device.
                //int bytesSent = handler.EndSend(ar);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... gps device.", WindowsServiceModel);
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();

            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - SendCallBack..... " + ex.Message, WindowsServiceModel);
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
        }

    }
}
